﻿using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;

namespace Dsl.Ped.Controls.SyntaxHighlighter
{
   /// <summary>
   /// This is the implementation of a <see cref="SyntaxHighlighterControl"/> that 
   /// adds new dependency property <see cref="TextProperty"/> to the <see cref="RichTextBox"/>. 
   /// A string can be bound into this property and thus displayed in a <see cref="RichTextBox"/>
   /// </summary>
   public class SyntaxHighlighterControl : RichTextBox, IRefreshableControl
   {
        /// <summary>
        /// Delimiter of the key word #keyWord#
        /// </summary>
        private char keyWordsDelimiter = '\u00a5';

        /// <summary>
        /// All keywords that should be marked significantly
        /// </summary>
        private IList<string> keyWords;
        
        /// <summary>
        /// Color which is used for the syntax highlighting
        /// </summary>
        private Color highlightingColor = Colors.Blue;

        /// <summary>
        /// Register new <see cref="TextProperty"/> which can bind a string into <see cref="RichTextBox"/> control
        /// </summary>
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text",
            typeof(string),
            typeof(SyntaxHighlighterControl),
            new FrameworkPropertyMetadata(
                string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
                    new PropertyChangedCallback(OnTextPropertyChanged),
                    new CoerceValueCallback(CoerceTextProperty),
                    true,
                    UpdateSourceTrigger.LostFocus));

        /// <summary>
        /// Gets or sets a <see cref="TextProperty"/> of the control
        /// </summary>
        public string Text {
            get
            {
                var result = (string)this.GetValue(TextProperty);
                return result;
            }
            set
            {
                this.SetValue(TextProperty, value);
            }
        }
        
        /// <summary>
        /// Gets or sets keyWords that should be syntacticaly highlighted
        /// </summary>
        public IList<string> KeyWords
        {
            get
            {
                return keyWords;
            }
            set
            {
                keyWords = value;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="Color"/> that is used for syntax highlighting
        /// </summary>
        public Color HighlightingColor
        {
            get
            {
                return highlightingColor;
            }
            set
            {
                highlightingColor = value;
            }
        }

        /// <summary>
        /// Delimiter of the keys. Key words are in the format #keyWord#, where # is keyword.
        /// The delimiter must be a unique string!
        /// </summary>
        public char KeyWordsDelimiter
        {
            get
            {
                return this.keyWordsDelimiter;
            }
            set
            {
                this.keyWordsDelimiter = value;
            }
        }
 
        /// <summary>
        /// Handler of TextChagned event
        /// </summary>
        /// <param name="dependencyObj"></param>
        /// <param name="eventArgs"></param>
        private static void OnTextPropertyChanged(
            DependencyObject dependencyObject,
            DependencyPropertyChangedEventArgs eventArgs) {
            
            SyntaxHighlighterControl syntaxHighlighterControl = (SyntaxHighlighterControl)dependencyObject;
            var newString = (string)eventArgs.NewValue;
            syntaxHighlighterControl.Document = GetFlowDocument(
                newString, 
                syntaxHighlighterControl.KeyWords, 
                syntaxHighlighterControl.KeyWordsDelimiter,
                syntaxHighlighterControl.HighlightingColor);
        }
 
        /// <summary>
        /// Coerce the <see cref="TextProperty"/>
        /// </summary>
        /// <param name="dependencyObj"></param>
        /// <param name="value"></param>
        /// <returns>If the inputString is null, return empty string</returns>
        private static object CoerceTextProperty(DependencyObject dependencyObj, object value) 
        {
            return value ?? string.Empty;
        }

        /// <summary>
        /// Get the <see cref="FlowDocument"/> used by <see cref="RichTextBox"/>.
        /// </summary>
        /// <param name="inputString">String value that should be displayed in a <see cref="RichTextBox"/></param>
        /// <param name="keyWords">Key words that should be highlighted</param>
        /// <param name="delimiter">Delimiter which surrounds each key word</param>
        /// <param name="highlightingColor">Color that highlights the key words</param>
        /// <returns><see cref="FlowDocument"/> that contains highlighted <see cref="inputText"/></returns>
        private static FlowDocument GetFlowDocument(string inputString, IList<string> keyWords, char delimiter, Color highlightingColor)
        {
            FlowDocument doc = new FlowDocument();
            var paragraph = new Paragraph();
            
            string[] tokens = inputString.Split(new char[] { delimiter });
            bool isToken = false;
            foreach (var token in tokens)
            {
                var run = new Run(token);
                if (isToken)
                {
                    if (isKeyWord(token, keyWords))
                    {
                        run.Foreground = new SolidColorBrush(highlightingColor);
                        run.FontWeight = FontWeights.Bold;
                    }
                }

                paragraph.Inlines.Add(run);
                isToken = !isToken;
            }

            doc.Blocks.Add(paragraph);

            return doc;
        }

        /// <summary>
        /// Check if the given word is a keyword in a collection
        /// </summary>
        /// <param name="word">Word to check</param>
        /// <param name="keyWords">Keywords</param>
        /// <returns>True, if <see cref="word"/> is in <see cref="keyWords"/>, False otherwise</returns>
        private static bool isKeyWord(string word, IList<string> keyWords)
        {
            if (keyWords != null)
            {
                return keyWords.Contains(word);
            }
            return false;
        }

       #region IRefreshableControl
       
       /// <summary>
       /// Refresh the binding of a <see cref="Text"/> property. That results in update
       /// the text in accordance with the actual state of bound object.
       /// </summary>
       public void Refresh()
       {
           this.GetBindingExpression(SyntaxHighlighterControl.TextProperty).UpdateTarget();
       }

       #endregion
   }
}
